{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Аггрегация разметки датасета LCS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Аггрегация строится по следующей системе:\n",
    "\n",
    "1. Сбор размеченных пулов с Толоки. Возможны варианты:\n",
    "    - только общий пул нужно аггрегировать, тогда забирается только он\n",
    "    - часть данных находится в контрольных заданиях и экзамене, тогда к основному пулу добавляются данные задания\n",
    "2. Фильтрация разметчиков:\n",
    "    - в общем пуле есть некоторое количество заранее размеченных заданий - контрольных\n",
    "    - хорошим считается разметчик, который показывает `accuracy >= 0.5` на данных заданиях\n",
    "    - формируется список \"плохих\" разметчиков\n",
    "3. Аггрегация ответов разметчиков по заданиям:\n",
    "    - форматирование в заданиях может отличаться от изначального из-за выгрузки с Толоки\n",
    "    - учитываются только ответы \"хороших\" разметчиков\n",
    "    - аггрегация по подготовленным пулам - создается массив карточек вида {key: value}, где key - кортеж из всех значимых элементов задания, value - список из кортежей вида (user_id, answer)\n",
    "4. Голосование большинством по каждому заданию:\n",
    "    - минимально необходимое большинство составляет 3 голоса, так как такое большинство валидно для перекрытия 5\n",
    "    - по результату формируется датафрейм с заданиями и ответами\n",
    "5. Подгрузка оригинальных данных с разметкой в виде таблицы с заданиями и ответами\n",
    "6. Соединение таблиц:\n",
    "    - очистка форматирования в таблице с ответами разметчиков и в таблице с правильными ответами\n",
    "    - создание единых столбцов с полным заданием\n",
    "    - соединение таблиц по данному столбцу\n",
    "    - валидация размеров\n",
    "7. Подсчет метрик"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "from collections import Counter\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сбор данных разметки и фильтрация разметчиков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Датасет проходил разметку одним пулом из 100 сгенерированных специально объектов в формате оригинального датасета."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "assignments = pd.read_csv('test_pool.tsv', sep='\\t')\n",
    "skills = pd.read_csv('workerSkills.csv', sep='|')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разметчикам предлагалось на основании имеющихся двух строковых последовательностей ответить на вопрос, какова длина наибольшей общей подпоследовательности целым числом.\n",
    "\n",
    "Вход: \n",
    "- INPUT:string1 (пример: `CTXMKP`).\n",
    "- INPUT:string2 (пример: `OEOPDQRI`).\n",
    "\n",
    "Выход:\n",
    "- OUTPUT:length (целое число, например: `1`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>INPUT:string1</th>\n",
       "      <th>INPUT:string2</th>\n",
       "      <th>OUTPUT:length</th>\n",
       "      <th>GOLDEN:length</th>\n",
       "      <th>HINT:text</th>\n",
       "      <th>HINT:default_language</th>\n",
       "      <th>ASSIGNMENT:link</th>\n",
       "      <th>ASSIGNMENT:task_id</th>\n",
       "      <th>ASSIGNMENT:assignment_id</th>\n",
       "      <th>ASSIGNMENT:task_suite_id</th>\n",
       "      <th>ASSIGNMENT:worker_id</th>\n",
       "      <th>ASSIGNMENT:status</th>\n",
       "      <th>ASSIGNMENT:started</th>\n",
       "      <th>ASSIGNMENT:submitted</th>\n",
       "      <th>ASSIGNMENT:accepted</th>\n",
       "      <th>ASSIGNMENT:reward</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>HNPUSKVDLLLKOS</td>\n",
       "      <td>VQJSBYODNEITFC</td>\n",
       "      <td>1</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>https://platform.toloka.ai/task/41565705/00027...</td>\n",
       "      <td>00027a3e09--6516f36c15b13a5af6053978</td>\n",
       "      <td>00027a3e09--6516f6510ab55c42a0d8d0b0</td>\n",
       "      <td>00027a3e09--6516f6510ab55c42a0d8d0ae</td>\n",
       "      <td>4c4133aeef5b4ac1092c735ede3147ac</td>\n",
       "      <td>APPROVED</td>\n",
       "      <td>2023-09-29T16:07:45.508</td>\n",
       "      <td>2023-09-29T16:08:40.445</td>\n",
       "      <td>2023-09-29T16:08:40.445</td>\n",
       "      <td>0.029</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    INPUT:string1   INPUT:string2  OUTPUT:length  GOLDEN:length  HINT:text  \\\n",
       "0  HNPUSKVDLLLKOS  VQJSBYODNEITFC              1            NaN        NaN   \n",
       "\n",
       "   HINT:default_language                                    ASSIGNMENT:link  \\\n",
       "0                    NaN  https://platform.toloka.ai/task/41565705/00027...   \n",
       "\n",
       "                     ASSIGNMENT:task_id              ASSIGNMENT:assignment_id  \\\n",
       "0  00027a3e09--6516f36c15b13a5af6053978  00027a3e09--6516f6510ab55c42a0d8d0b0   \n",
       "\n",
       "               ASSIGNMENT:task_suite_id              ASSIGNMENT:worker_id  \\\n",
       "0  00027a3e09--6516f6510ab55c42a0d8d0ae  4c4133aeef5b4ac1092c735ede3147ac   \n",
       "\n",
       "  ASSIGNMENT:status       ASSIGNMENT:started     ASSIGNMENT:submitted  \\\n",
       "0          APPROVED  2023-09-29T16:07:45.508  2023-09-29T16:08:40.445   \n",
       "\n",
       "       ASSIGNMENT:accepted  ASSIGNMENT:reward  \n",
       "0  2023-09-29T16:08:40.445              0.029  "
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "assignments.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Фильтруем толокеров, которые дали меньше половины корректных ответов на контрольных заданиях."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Users total: 29\n",
      "Bad users: 3\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "users_dict = defaultdict(lambda: defaultdict(int))\n",
    "\n",
    "for idx, row in assignments.iterrows():\n",
    "    s1 = row[0]\n",
    "    s2 = row[1]\n",
    "\n",
    "    out = row[2]\n",
    "    \n",
    "    gold = row[3]\n",
    "\n",
    "    user = row[10]\n",
    "\n",
    "    if str(user) != \"nan\" and str(gold) != \"nan\":\n",
    "        if out == int(gold):\n",
    "            users_dict[user][\"good\"] += 1\n",
    "        else:\n",
    "            users_dict[user][\"bad\"] += 1\n",
    "\n",
    "print(f\"Users total: {len(users_dict)}\")\n",
    "bad_users = []\n",
    "for key, value in users_dict.items():\n",
    "    percentage_good = value[\"good\"]/(value[\"good\"] + value[\"bad\"])\n",
    "    if percentage_good < 0.5:\n",
    "        bad_users.append(key)\n",
    "\n",
    "print(f\"Bad users: {len(bad_users)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3 из 29 разметчиков на контрольных заданиях показали слишком плохое качество, чтобы учитывать их ответы для расчета метрики."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь нужно оставить только основной пул. Контролки генерировались отдельно от самой подвыборки, поэтому их учитывать нельзя. На контрольных заданиях есть `GOLDEN:length`. Также отсеиваем возможные баги Толоки, когда в строке может не быть задания - `INPUT:string1` содержит NaN."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "assignments_no_control = assignments[assignments['GOLDEN:length'].isnull()]\n",
    "assignments_no_control_no_null = assignments_no_control[assignments_no_control['INPUT:string1'].notnull()]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посчитаем, сколько было затрачено на получение разметки тестовых данных без учета контрольных заданий, так как они могли проходиться неограниченное количество раз одним и тем же разметчиком."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "взвешенная цена айтема в тесте: 0.029\n",
      "потрачено на разметку теста: 14.5\n",
      "14.5 / 0.029\n"
     ]
    }
   ],
   "source": [
    "def w_sum(df):\n",
    "    idx = df.index.values\n",
    "    vals = df.values\n",
    "    summ = idx * vals\n",
    "    return summ.sum()\n",
    "\n",
    "d1 = assignments_no_control_no_null['ASSIGNMENT:reward'].value_counts(normalize=True)\n",
    "d2 = assignments_no_control_no_null['ASSIGNMENT:reward'].value_counts()\n",
    "print(f'взвешенная цена айтема в тесте: {round(w_sum(d1), 3)}')\n",
    "print(f'потрачено на разметку теста: {round(w_sum(d2), 3)}')\n",
    "print(f'{round(w_sum(d2), 3)} / {round(w_sum(d1), 3)}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выделим, сколько составила средняя часовая ставка для разметки тестовой части датасета. Это будет простое среднее из следующих величин: количество заданий, которое разметчк может сделать за час на основе данного задания, помноженное на цену задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.7303670255451353"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def get_hour_pay(df):\n",
    "    try:\n",
    "        times = pd.to_datetime(df['ASSIGNMENT:submitted']) - pd.to_datetime(df['ASSIGNMENT:started'])\n",
    "    except Exception as e:\n",
    "        times = []\n",
    "        for i in range(len(df)):\n",
    "            try:\n",
    "                start = pd.to_datetime(df['ASSIGNMENT:started'].iloc[i])\n",
    "            except Exception as e:\n",
    "                start = pd.to_datetime(df['ASSIGNMENT:started'].apply(lambda x: x.split('T')[1]).iloc[i])\n",
    "            try:\n",
    "                end = pd.to_datetime(df['ASSIGNMENT:submitted'].iloc[i])\n",
    "            except Exception as e:\n",
    "                start = pd.to_datetime(df['ASSIGNMENT:submitted'].apply(lambda x: x.split('T')[1]).iloc[i])\n",
    "            delta = end - start\n",
    "            times.extend([delta])\n",
    "        times = pd.Series(times)\n",
    "        # times = pd.to_datetime(df['ASSIGNMENT:submitted'].apply(lambda x: x.split('T')[1])) - pd.to_datetime(df['ASSIGNMENT:started'].apply(lambda x: x.split('T')[1]))\n",
    "    sums = 3600 / times.apply(lambda x: x.seconds) * df['ASSIGNMENT:reward']\n",
    "    return sums.mean()\n",
    "\n",
    "get_hour_pay(assignments_no_control_no_null)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сбор ответов разметчиков и голосование"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Собираем ответы голосования большинством для каждого задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "text_dict = defaultdict(list)\n",
    "\n",
    "for s1, s2, user, out in zip(\n",
    "    assignments_no_control_no_null[\"INPUT:string1\"], assignments_no_control_no_null[\"INPUT:string2\"], \n",
    "    assignments_no_control_no_null[\"ASSIGNMENT:worker_id\"], assignments_no_control_no_null[\"OUTPUT:length\"]\n",
    "    ):\n",
    "    if user not in bad_users:\n",
    "        text_dict[(s1, s2)].append([\n",
    "                user,\n",
    "                {\"out\": out}\n",
    "        ])\n",
    "\n",
    "print(len(text_dict))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Counter({5: 92, 4: 8})"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "keys = list(text_dict.keys())\n",
    "Counter([len(text_dict[keys[i]]) for i in range(len(keys))])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Есть 8 заданий с перекрытием 4. Для формирования итоговых лейблов нужно, чтобы было простое большинство разметчиков, проголосовавших за данную опцию. Если большинства нет, то оценка строится, исходя из оценки навыков разметчиков. В таком случае, финальный лейбл будет присвоен по голосу группы с наилучшими навыками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "preds_full = {}\n",
    "user2skill = {k:v for k, v in zip(skills['worker_id'], skills['skill_value'])}\n",
    "control_acc = assignments[assignments['GOLDEN:length'].notna()]\\\n",
    "    .groupby('ASSIGNMENT:worker_id')\\\n",
    "        .apply(lambda x: (np.array(x['OUTPUT:length']) == np.array(x['GOLDEN:length'])).mean())\n",
    "user2control = {k:v for k, v in zip(control_acc.index, control_acc.values)}\n",
    "\n",
    "from crowdkit.aggregation.classification.glad import GLAD\n",
    "\n",
    "full = assignments['INPUT:string1'] + ' ' + assignments['INPUT:string2']\n",
    "id2task = dict(enumerate(full))\n",
    "task2id = {k:v for v, k in id2task.items()}\n",
    "id2user = dict(enumerate(assignments['ASSIGNMENT:worker_id']))\n",
    "user2id = {k:v for v, k in id2user.items()}\n",
    "\n",
    "codes = full.map(task2id)\n",
    "res = pd.DataFrame({'task': codes, 'worker': assignments['ASSIGNMENT:worker_id'].map(user2id), 'label': assignments['OUTPUT:length']})\n",
    "model = GLAD(n_iter=10000, tol=1e-06, m_step_max_iter=1000, m_step_tol=1e-03)\n",
    "model.fit(res)\n",
    "user2alpha = dict(enumerate(model.alphas_))\n",
    "tb = model.alphas_.copy()\n",
    "tb.index = tb.index.map(id2user)\n",
    "user2alpha = {k:v for k, v in zip(tb.index, tb.values)}\n",
    "\n",
    "stats = {\n",
    "    'total_agreement': 0,\n",
    "    'majority': 0,\n",
    "    'skill_based': 0,\n",
    "    'major_based': 0,\n",
    "    'em_based': 0,\n",
    "    'rest': 0,\n",
    "}\n",
    "\n",
    "for i in range(len(keys)):\n",
    "    ans = text_dict[keys[i]]\n",
    "    lst = [[ans[j][0], ans[j][1]['out']] for j in range(len(ans))]\n",
    "    users, votes = list(zip(*lst))\n",
    "    cnt = pd.Series(Counter(votes)).sort_values(ascending=False)\n",
    "\n",
    "    # # total agreement\n",
    "    if len(cnt) == 1:\n",
    "        res = cnt.index[0]\n",
    "        stats['total_agreement'] += 1\n",
    "    # simple majority\n",
    "    elif cnt.iloc[0] > cnt.iloc[1]:\n",
    "        res = cnt.index[0]\n",
    "        stats['majority'] += 1\n",
    "    # (> 1 options) & (1 option == 2 option)\n",
    "    else:\n",
    "        # try overall skill based comparison\n",
    "        vals = list(map(lambda x: user2skill[x], users))\n",
    "        table = pd.DataFrame({'user': users, 'votes': votes, 'skill': vals})\n",
    "        agg = table.groupby('votes').agg(\n",
    "            sum_skill=pd.NamedAgg(column='skill', aggfunc='sum'),\n",
    "            sum_votes=pd.NamedAgg(column='user', aggfunc='count')\n",
    "        ).sort_values(by=['sum_votes', 'sum_skill'], ascending=False)\n",
    "        # check there is a leader by skills\n",
    "        if agg['sum_skill'].iloc[0] > agg['sum_skill'].iloc[1]:\n",
    "            res = agg.index[0]\n",
    "            stats['skill_based'] += 1\n",
    "        else:\n",
    "            # top-3 answers by overall skills\n",
    "            vals = list(map(lambda x: user2skill[x], users))\n",
    "            table = pd.DataFrame({'user': users, 'votes': votes, 'skill': vals})\n",
    "            table = table.sort_values(by='skill', ascending=False)\n",
    "            if len(table) >= 3:\n",
    "                sub = table.iloc[:3]\n",
    "            else:\n",
    "                sub = table\n",
    "            agg = sub.groupby('votes').agg(\n",
    "                sum_skill=pd.NamedAgg(column='skill', aggfunc='sum'),\n",
    "                sum_votes=pd.NamedAgg(column='user', aggfunc='count')\n",
    "            ).sort_values(by=['sum_votes', 'sum_skill'], ascending=False)\n",
    "            if agg['sum_skill'].iloc[0] != agg['sum_skill'].iloc[1]:\n",
    "                res = agg.index[0]\n",
    "                stats['major_based'] += 1\n",
    "            \n",
    "            else:\n",
    "                vals = list(map(lambda x: user2alpha[x], users))\n",
    "                table = pd.DataFrame({'user': users, 'votes': votes, 'skill': vals})\n",
    "                agg = table.groupby('votes').agg(\n",
    "                    sum_skill=pd.NamedAgg(column='skill', aggfunc='sum'),\n",
    "                    sum_votes=pd.NamedAgg(column='user', aggfunc='count')\n",
    "                ).sort_values(by=['sum_votes', 'sum_skill'], ascending=False)\n",
    "                # check there is a leader by skills\n",
    "                if agg['sum_skill'].iloc[0] != agg['sum_skill'].iloc[1]:\n",
    "                    res = agg.index[0]\n",
    "                    stats['em_based'] += 1\n",
    "                else:\n",
    "                    res = agg.index[0]\n",
    "                    stats['rest'] += 1\n",
    "\n",
    "    preds_full[keys[i]] = res\n",
    "\n",
    "preds_full_df = pd.concat([pd.DataFrame(preds_full.keys(), columns=['s1', 's2']), pd.DataFrame(preds_full.values(), columns=['lb'])], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'total_agreement': 22,\n",
       " 'majority': 58,\n",
       " 'skill_based': 15,\n",
       " 'major_based': 0,\n",
       " 'em_based': 5,\n",
       " 'rest': 0}"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Сопоставление разметки и ground truth"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Забираем задания из датасета с правильными метками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "res_df = pd.read_csv('full_with_ans.tsv', sep='\\t')\n",
    "res_df = res_df.rename({'INPUT:string1': 's1', 'INPUT:string2': 's2', 'target': 'lb'}, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После скачивания с Толоки в текстах рушится форматирование, потому нельзя просто сделать join двух табличек. Нужно убрать все \"лишнее\" форматирование сразу из двух табличек, чтобы остались только тексты, пунктуация и пробелы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def format_text(text):\n",
    "    text = (text.strip().replace('\\n', ' ').replace('\\t', ' ')\n",
    "            .replace('\\r', ' ').replace('  ', ' ').replace('  ', ' ')\n",
    "            .replace('  ', ' '))\n",
    "    return text\n",
    "\n",
    "res_df['s1'] = res_df['s1'].apply(format_text)\n",
    "res_df['s2'] = res_df['s2'].apply(format_text)\n",
    "\n",
    "preds_full_df['s1'] = preds_full_df['s1'].apply(format_text)\n",
    "preds_full_df['s2'] = preds_full_df['s2'].apply(format_text)\n",
    "\n",
    "res_df['full'] = res_df['s1'] + ' ' + res_df['s2']\n",
    "preds_full_df['full'] = preds_full_df['s1'] + ' ' + preds_full_df['s2']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Делаем left join, чтобы соединить голосование и правильные метки для одних и тех же заданий."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "new = res_df.merge(preds_full_df.drop(['s1', 's2'], axis=1), on='full', how='left')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_valid = new[new['lb_y'].notna()].copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100\n"
     ]
    }
   ],
   "source": [
    "print(len(new_valid))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После соединения меток голосования и правильных меток осталось 54 непустых строки. Значит, ни один объект выборки после фильтрации не был утерян."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>lb_x</th>\n",
       "      <th>s1</th>\n",
       "      <th>s2</th>\n",
       "      <th>full</th>\n",
       "      <th>lb_y</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>6</td>\n",
       "      <td>PPAMZHQONHYTUELNEUXTN</td>\n",
       "      <td>MWQCYPKFNGWQBENFBVRH</td>\n",
       "      <td>PPAMZHQONHYTUELNEUXTN MWQCYPKFNGWQBENFBVRH</td>\n",
       "      <td>9</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   lb_x                     s1                    s2  \\\n",
       "0     6  PPAMZHQONHYTUELNEUXTN  MWQCYPKFNGWQBENFBVRH   \n",
       "\n",
       "                                         full  lb_y  \n",
       "0  PPAMZHQONHYTUELNEUXTN MWQCYPKFNGWQBENFBVRH     9  "
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_valid.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Подсчет метрики"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.56"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_valid['lb_y'] = new_valid['lb_y'].astype(int)\n",
    "(new_valid['lb_x'] == new_valid['lb_y']).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`Accuracy = 0.56`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Анализ ошибок"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# !pip install crowd-kit\n",
    "\n",
    "from scipy import stats\n",
    "import numpy as np\n",
    "from crowdkit.aggregation.classification.glad import GLAD\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from matplotlib.gridspec import GridSpec as grid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заново подгружаем пул разметки."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "pool = pd.read_csv('assignments_from_pool_41565705__29-09-2023.tsv', sep='\\t')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Функция для подсчета энтропии (рассогласованности) в разметке."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "def entropy(arr):\n",
    "    arr = np.asarray(arr).reshape(-1,)\n",
    "    cats = np.unique(arr)\n",
    "    probs = np.array([(arr == val).mean() for val in cats])\n",
    "    if (probs == 1).sum():\n",
    "        return 0\n",
    "    return -(probs * np.log2(probs)).sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Убираем контрольные задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "pool = pool[pool['GOLDEN:length'].isna()].reset_index(drop=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "pool['full'] = pool['INPUT:string1'] + ' ' + pool['INPUT:string2']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Собираем полное условие и все лейблы от разметчиков в одну таблицу. Фильтрации по \"плохим\" разметчикам не проводим, чтобы и их ошибки видеть."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {}\n",
    "for i in range(len(pool)):\n",
    "    obj = pool.iloc[i]\n",
    "    d[obj['full']] = d.get(obj['full'], [])\n",
    "    d[obj['full']].append(int(obj['OUTPUT:length']))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>full</th>\n",
       "      <th>labels</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>HNPUSKVDLLLKOS VQJSBYODNEITFC</td>\n",
       "      <td>[1, 7, 12, 1, 5]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>ETZWYRWEKQPJLU OEOPDQRI</td>\n",
       "      <td>[2, 4, 4, 2, 2]</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>ISOGEVZOTTUQDOCK BZGXQTGWLECXUEZPVWIKQILONSZ</td>\n",
       "      <td>[14, 4, 5, 4, 6]</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                           full            labels\n",
       "0                 HNPUSKVDLLLKOS VQJSBYODNEITFC  [1, 7, 12, 1, 5]\n",
       "1                       ETZWYRWEKQPJLU OEOPDQRI   [2, 4, 4, 2, 2]\n",
       "2  ISOGEVZOTTUQDOCK BZGXQTGWLECXUEZPVWIKQILONSZ  [14, 4, 5, 4, 6]"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.DataFrame({'full': d.keys(), 'labels': d.values()})\n",
    "df.head(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сразу посчитаем длины входных строк."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['s1'] = df.full.apply(lambda x: x.split()[0])\n",
    "df['s2'] = df.full.apply(lambda x: x.split()[1])\n",
    "df['full_len'] = df['full'].apply(len)\n",
    "df['s1_len'] = df['s1'].apply(len)\n",
    "df['s2_len'] = df['s2'].apply(len)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь считаем энтропию разметки для каждого задания."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['entropy'] = df['labels'].apply(entropy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "res_df = pd.read_csv('full_with_ans.tsv', sep='\\t')\n",
    "res_df['full'] = res_df['INPUT:string1'] + ' ' + res_df['INPUT:string2']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Собираем статистики и правильные ответы в одну табличку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.merge(res_df, on='full', how='left')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.drop(['INPUT:string1', 'INPUT:string2'], axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "def corrs(arr1, arr2):\n",
    "    return {\n",
    "        \"Pearson corr\": stats.pearsonr(arr1, arr2)[0],\n",
    "        \"Spearman corr\": stats.spearmanr(arr1, arr2)[0],\n",
    "        \"Kendall corr\": stats.kendalltau(arr1, arr2)[0],\n",
    "    }"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Корреляция с энтропией\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Pearson corr</th>\n",
       "      <th>Spearman corr</th>\n",
       "      <th>Kendall corr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Общая длина двух последовательностей</th>\n",
       "      <td>0.622</td>\n",
       "      <td>0.622</td>\n",
       "      <td>0.476</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина первой последовательности</th>\n",
       "      <td>0.535</td>\n",
       "      <td>0.526</td>\n",
       "      <td>0.405</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина второй последовательности</th>\n",
       "      <td>0.344</td>\n",
       "      <td>0.357</td>\n",
       "      <td>0.276</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина LCS</th>\n",
       "      <td>0.725</td>\n",
       "      <td>0.755</td>\n",
       "      <td>0.628</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                      Pearson corr  Spearman corr  \\\n",
       "Общая длина двух последовательностей         0.622          0.622   \n",
       "Длина первой последовательности              0.535          0.526   \n",
       "Длина второй последовательности              0.344          0.357   \n",
       "Длина LCS                                    0.725          0.755   \n",
       "\n",
       "                                      Kendall corr  \n",
       "Общая длина двух последовательностей         0.476  \n",
       "Длина первой последовательности              0.405  \n",
       "Длина второй последовательности              0.276  \n",
       "Длина LCS                                    0.628  "
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"Корреляция с энтропией\")\n",
    "pd.DataFrame({\n",
    "    \"Общая длина двух последовательностей\": corrs(df.entropy, df['full_len']),\n",
    "    \"Длина первой последовательности\": corrs(df.entropy, df['s1_len']),\n",
    "    \"Длина второй последовательности\": corrs(df.entropy, df['s2_len']),\n",
    "    \"Длина LCS\": corrs(df.entropy, df['target']),\n",
    "}).round(3).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассогласованность разметки достаточно сильно скоррелирована с длиной входных строк. Причем очевидно, что первая строка имеет большее значение, чем вторая. Чем больше размер общей подпоследовательности, тем больше энтропия в ответах."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Попробуем теперь посмотреть на то, сойдутся ли ошибки разметчиков с аггрегацией данных разметки EM-алгоритмом."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "id2task = dict(enumerate(df.full))\n",
    "task2id = {k:v for v, k in id2task.items()}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "id2user = dict(enumerate(pool['ASSIGNMENT:worker_id']))\n",
    "user2id = {k:v for v, k in id2user.items()}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['code'] = df.full.map(task2id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "res = pool[['full', 'ASSIGNMENT:worker_id', 'OUTPUT:length']].rename({'full': 'task', 'ASSIGNMENT:worker_id': 'worker', 'OUTPUT:length': 'label'}, axis=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "res.task = res.task.map(task2id)\n",
    "res.worker = res.worker.map(user2id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = GLAD(n_iter=10000, tol=1e-06, m_step_max_iter=1000, m_step_tol=1e-03)\n",
    "model.fit(res);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "task2beta = dict(enumerate(model.betas_))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Беты у GLAD - это обратные сложности заданий, посчитанные по полу. При этом правльные ответы алгоритму не известны, чтобы повлиять на его результаты."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['beta'] = df.code.apply(lambda x: task2beta[x])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "df['beta'] = df.code.apply(lambda x: task2beta[x])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Корреляция с энтропией\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Kendall corr</th>\n",
       "      <th>Pearson corr</th>\n",
       "      <th>Spearman corr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Обратные сложности из GLAD</th>\n",
       "      <td>-0.341</td>\n",
       "      <td>-0.43</td>\n",
       "      <td>-0.457</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            Kendall corr  Pearson corr  Spearman corr\n",
       "Обратные сложности из GLAD        -0.341         -0.43         -0.457"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"Корреляция с энтропией\")\n",
    "pd.DataFrame({\n",
    "    \"Обратные сложности из GLAD\": corrs(df.entropy, df['beta']),\n",
    "}).round(3).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посчитанные в EM-алгоритме сложности заданий (беты) также сильно коррелируют с энтропией. Однако алгоритм и учится на результатах разметки, так что это не вполне убедительно, хоть и подтверждает, что сложные задания и есть самые рассогласованные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Корреляция с обратными сложностями\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Pearson corr</th>\n",
       "      <th>Spearman corr</th>\n",
       "      <th>Kendall corr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Длина первой последовательности</th>\n",
       "      <td>-0.167</td>\n",
       "      <td>-0.150</td>\n",
       "      <td>-0.098</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина второй последовательности</th>\n",
       "      <td>-0.102</td>\n",
       "      <td>-0.128</td>\n",
       "      <td>-0.085</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Суммарная длина</th>\n",
       "      <td>-0.191</td>\n",
       "      <td>-0.185</td>\n",
       "      <td>-0.130</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                 Pearson corr  Spearman corr  Kendall corr\n",
       "Длина первой последовательности        -0.167         -0.150        -0.098\n",
       "Длина второй последовательности        -0.102         -0.128        -0.085\n",
       "Суммарная длина                        -0.191         -0.185        -0.130"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"Корреляция с обратными сложностями\")\n",
    "pd.DataFrame({\n",
    "    \"Длина первой последовательности\": corrs(df.s1_len, df['beta']),\n",
    "    \"Длина второй последовательности\": corrs(df.s2_len, df['beta']),\n",
    "    \"Суммарная длина\": corrs(df.full_len, df['beta']),\n",
    "}).round(3).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Выводы подтверждаются. Длина влияет на сложность задачи. В отличии от предыдущих величин, данные никак моделью не могли быть учтены, так как модель не видела самих заданий, а только ответы разметчиков. Также обратить вннимание стоит, что бета - это инвертированная сложность задания, потому отрицательная корреляция подтверждает выводы, а не опровергает их."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Корреляция с LCS\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Pearson corr</th>\n",
       "      <th>Spearman corr</th>\n",
       "      <th>Kendall corr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Обратные сложности из GLAD</th>\n",
       "      <td>-0.314</td>\n",
       "      <td>-0.295</td>\n",
       "      <td>-0.212</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Средний ответ разметчика</th>\n",
       "      <td>0.888</td>\n",
       "      <td>0.909</td>\n",
       "      <td>0.794</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                            Pearson corr  Spearman corr  Kendall corr\n",
       "Обратные сложности из GLAD        -0.314         -0.295        -0.212\n",
       "Средний ответ разметчика           0.888          0.909         0.794"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"Корреляция с LCS\")\n",
    "pd.DataFrame({\n",
    "    \"Обратные сложности из GLAD\": corrs(df.target, df['beta']),\n",
    "    \"Средний ответ разметчика\": corrs(df.labels.apply(np.mean), df['target']),\n",
    "}).round(3).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Достаточно сильная отрицательная корреляция между инвертированной сложностью задания и величиной таргета - размером общей подпоследовательности."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В среднем разметчики неплохо приближают значение таргета. Это значит, что разметчики очень хорошо угадывают примерную длину общей подпоследовательности."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Корреляция со средним ответом разметчиков на задание\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Pearson corr</th>\n",
       "      <th>Spearman corr</th>\n",
       "      <th>Kendall corr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Суммарная длина</th>\n",
       "      <td>0.786</td>\n",
       "      <td>0.796</td>\n",
       "      <td>0.622</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина первой последовательности</th>\n",
       "      <td>0.580</td>\n",
       "      <td>0.630</td>\n",
       "      <td>0.460</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Длина второй последовательности</th>\n",
       "      <td>0.538</td>\n",
       "      <td>0.505</td>\n",
       "      <td>0.366</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                 Pearson corr  Spearman corr  Kendall corr\n",
       "Суммарная длина                         0.786          0.796         0.622\n",
       "Длина первой последовательности         0.580          0.630         0.460\n",
       "Длина второй последовательности         0.538          0.505         0.366"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"Корреляция со средним ответом разметчиков на задание\")\n",
    "pd.DataFrame({\n",
    "    \"Суммарная длина\": corrs(df.labels.apply(np.mean), df['full_len']),\n",
    "    \"Длина первой последовательности\": corrs(df.labels.apply(np.mean), df['s1_len']),\n",
    "    \"Длина второй последовательности\": corrs(df.labels.apply(np.mean), df['s2_len']),\n",
    "}).round(3).transpose()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чем больше длина входных строк, тем большее число разметчики ставят в разметке. При этом можно заметить, что снова первая строка влияет на длину ответа разметчиков сильнее, чем вторая строка."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Проведем снова голосование большинством. Для тех заданий, где согласованность не достигнута, будем ставить отметку -1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "lst = []\n",
    "for i in range(len(df)):\n",
    "    obj = df.labels.iloc[i]\n",
    "    cnt = Counter(obj)\n",
    "    most = cnt.most_common(1)[0][1]\n",
    "    if most >= 3:\n",
    "        lst.append(cnt.most_common(1)[0][0])\n",
    "    else:\n",
    "        lst.append(-1)\n",
    "df['lb'] = lst"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Будем грубо оценивать вероятность правильного ответа на задание на основе имеющихся ответов - частотный подход (число поизитивных исходов / число всех исходов)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_prob(df, tgt, feat):\n",
    "    mask = df[feat] == tgt\n",
    "    sub = df[mask].copy()\n",
    "    return (sub['lb'] == tgt).sum() / len(sub)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Probability for target ->0<- is equal to ->1.0<-\n",
      "Probability for target ->1<- is equal to ->1.0<-\n",
      "Probability for target ->2<- is equal to ->0.7<-\n",
      "Probability for target ->3<- is equal to ->0.5<-\n",
      "Probability for target ->4<- is equal to ->0.31<-\n",
      "Probability for target ->5<- is equal to ->0.07<-\n",
      "Probability for target ->6<- is equal to ->0.0<-\n",
      "Probability for target ->7<- is equal to ->0.0<-\n",
      "Probability for target ->8<- is equal to ->0.0<-\n",
      "Probability for target ->9<- is equal to ->0.0<-\n",
      "Probability for target ->10<- is equal to ->0.0<-\n",
      "Probability for target ->11<- is equal to ->0.0<-\n"
     ]
    }
   ],
   "source": [
    "l = []\n",
    "for i in sorted(df.target.unique()):\n",
    "    proba = get_prob(df, i, 'target')\n",
    "    print(f'Probability for target ->{i}<- is equal to ->{round(proba, 2)}<-')\n",
    "    l.append(proba)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чем больше таргет, тем меньше мы оцениваем вероятность того, что толокеры успешно справятся с задачей. Причем при длине общей последовательности больше 5 наша оценка вероятности стремится к нулю"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь оценим вероятность правильного ответа для разных длин строк"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_prob1(df, tgt, feat):\n",
    "    mask = df[feat] == tgt\n",
    "    sub = df[mask].copy()\n",
    "    return (sub['lb'] != -1).sum() / len(sub)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "l1 = []\n",
    "for i in sorted(df['full_len'].unique()):\n",
    "    proba = get_prob1(df, i, 'full_len')\n",
    "    l1.append(proba)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "l2 = []\n",
    "for i in sorted(df['s1_len'].unique()):\n",
    "    proba = get_prob1(df, i, 's1_len')\n",
    "    l2.append(proba)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "l3 = []\n",
    "for i in sorted(df['s2_len'].unique()):\n",
    "    proba = get_prob1(df, i, 's2_len')\n",
    "    l3.append(proba)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Посмотрим на результаты на графике для удобства."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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1auXDMPQL7/8ol9//VWS9Mcff2jNmjW65ZZbio332WeftfkZLsqFVy8KniMr56e8GDx4sJYvX66VK1dKkpKSktS1a1eNGzdOe/bskST5+Pjo/fffV5UqVRza5syZM/XKK6+Yr319fdWuXTudOXNGaWlpMgxDOTk5GjhwoK644gp16NBBHTt2VM2aNc0xaWlpNlc3e/XqZbMPe7Hkf6/Onj2r1NRU83fTggULNGnSJEmSYRjq1auXNm3aZI5r0qSJGjVqpF27dplXiJYvX66kpCS9/vrruuyyy2z2f+rUKX3xxRfm66ZNm6pZs2bm6zZt2jh9HuzJ3/7p06eVkpJizlRYsGCBEhMT7Y4BYJFbyzgADnH0ncbAwEC7Vxlat25t9omKijL++usvc93u3buN4OBgc/1DDz1U5H49PT1ttn/hlbAuXbqY65KSkmzWJSUlmevS09ONOnXqmOu8vLyMP/74w1zv6+tr913a4t4hL6jgu9QXXgEo6tgcvSL1ww8/2Iy78sorjczMTMMwDCM3N9fo37+/zfoXX3zRHHvhu9lPPPGEkZOTY64/fvy4sXDhQrvxFncVoqS4hwwZYvfnxdkrUhdekSnufF94xaVbt25Gdna2YRiGcfjwYaNp06Y269evX2+O3bNnj3H06NFCseTk5BgdOnQwx8TExBS7z3x///23TfvixYvNdfbO7+bNmw1PT89irxzkO3v2rE2fBx54wKnz44iycEXKMAxj//79NlecW7dubXh4eJivX3jhBYeOxzDOn7/LLrvM5orLxo0bzfVz5861ieWmm26yux1Hz2dR38/Jkyeb7UFBQWb7p59+ajNmwoQJNtsbNGiQze+z9PT0Qvt05PyX5jwUt/3Bgweb7c2bNy/yvACwhitSQAVy6tQp3Xjjjfrwww91zz33SJIyMzO1efNms4+Xl1ehdyO9vf/3q6DgO6YXuummm2zuu/n3v/+t8ePHKyMjQ5L0zTffKDc3V15eXjbb8ff319ixY83XkZGRGjRokJ555hlJUm5urr766is98MADkqQGDRqYVw4ulY8++kg///yz+frQoUN2+114foYPH66wsDBJkqenp1566SWb+1JWrFihZ555RpmZmYXezZ44caI8Pf93q2pQUJB69+7tisMx/fLLL4UeRuJOycnJ8vX1lSTVqFFDSUlJevjhh831a9asUWxs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//qXjh8/bnnaav7/lczMTPO+J+l8gVmcLl26FCowLjRz5kwNGTJEubm5Ztutt96qK664wlKM+fKvHOzfv9/moSUX3lvmCj///LMWLlyos2fPasmSJTbrmjVrpi1bthQ5NisrS/369TOPu3nz5nrzzTfVrl0782f+k08+0ezZs20eJlIUb29vjRo1yvzohuzsbHXo0MF87HfB3zWenp4aPXq05eMtSv7DJnJzc/XDDz/o+PHj5rr8n5GePXuqXbt2+v777yVJS5cuVWRkpKKjoxUUFKQDBw7op59+svswFCtceR7yHzZx4UcrFDwuAKV0CZ4MCKCUHH38uSQjICDA+PLLLwttY+bMmcV+IG/+8vzzzxe533vvvdfm0bwFl8TExEL7XL16tVG9evVi93fzzTcbp0+fNsecOnXKiIqKMtffeuut5rqy8PhzwzCMY8eOlfhBoOHh4cb3339faL9z5swx/Pz8ihyX/4G8F3Lm8eeSjOuuu87y+bOn4LjSPP684GOYCy41a9Y0tm/fbjO2a9euRZ6ju+66y6atuH3aW7y8vGw+gPrC8+vj42P88ssvJR7/heOuueYa4+DBg06fn6L+Txd8BLarHn9e1NKrVy/DMIr/eSn4wcre3t42Hz590003meuqVKli7Ny503DUhR+bcOHi4+NjzJkzp8jxzjz+vKilUaNGNh8mvH//fpuPkihq8fT0tLtPK4+fd+Y8OPr97dChg5Gbm1vkvgE4jql9QDmX/+ji6OhoDR8+XL/88ou6dOlSqN9jjz2mn376SY8//rhatGih4OBgeXl5qVq1amrdurUGDRqkL774otgb66+88kpt3rxZ8fHxCgsLk5+fn1q0aKHZs2drxowZhfp37txZ//3vf/Xss88qOjpawcHB8vb2Vnh4uHr06KGPPvpIn332mc3UlHHjxplPyAoNDbX5oN6yIiQkRCkpKXr33XfVrVs3hYWFydvbW8HBwYqJidGYMWP03//+1+70roEDB+rnn39WUlKSrr76agUHB8vHx0e1a9fWTTfdpMcff9xlcXp6etr9vrjTjBkzNG/ePEVHR8vf3181atTQfffdp02bNhW672nZsmV6+umnFRkZKR8fH0VGRurxxx/XN99849QVNm9vb9WqVUu333671qxZU+wHNScmJqpp06YlbtPLy0vh4eH617/+pddee00bNmwo9sORHZV/vPfff7++//77QlfoXMnDw0PBwcFq166dpk2bpg8//LDY/h9//LHNPVVPPfWUWrdubb6eO3euec/myZMn1bdv30KPhy/KlClTtHbtWt13332qV6+e/Pz8FBAQoMaNG+uRRx7RDz/8YGnKoFUBAQFq1qyZhg8frvXr19v8nNWqVUsbNmzQggUL1L17d0VERMjHx0f+/v6KiorSLbfcoilTpthMd3aWq89DlSpV1KpVK40ZM0YrVqy4JE8MBSoDD8P4/3NMAOACe/futXmsbnJyssaMGeO+gFDu9O/f3+bpe6QcAEBFwVsSAAAAAGARhRQAAAAAWEQhBQAAAAAWcY8UAAAAAFjEFSkAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoXTf/+/eXh4VHismDBAneHCgCohMhTAEqDQgoAAAAALKKQwiWxZs0aGYZhLsnJye4OCQAAE3kKgFUUUihT8vLy9Nprryk6OlqBgYGqUqWK2rZt69C0igULFpjTMJYtW6YBAwaoWrVqCg0NVf/+/XXs2DGz76ZNm3TrrbcqKipKVapUka+vrxo0aKDBgwfrn3/+Mfvt2LFDHTt21GWXXSYfHx9VqVJF11xzjRYtWmT2SU1NNfcbGhqqEydOmOvi4+PNdf3797eJ9+OPP9b111+vkJAQ+fv7q2XLlnrjjTdkGIbZZ8yYMeb4b7/9VnfeeaeCgoJUs2ZNJSUlKScnR5I0atQos9+uXbvM8YcOHZK3t7c8PDyUmJho97wV3EdRS2pqqn7//XfdfffdatSokUJCQuTr66vIyEj1799fGRkZNtsseMyzZ89Wo0aN5O/vrzZt2mjdunVmv7179xa5z23btkmSsrKy9PTTT6tx48by8/NTtWrV1LNnT23ZsqXQ/opaOnXqJEmaMmWK2rdvr/DwcPn6+io4OFjt2rXT/Pnz7Z4bALgQeerS56kLz11qaqokaeXKlYV+z0tSp06d5OHhoaioqGLHFzwvxeWOfv36ycPDQ4GBgcrKyjK3uWXLFrPvpEmTFBUVVez28uN577331LlzZ9WuXVt+fn4KDAxUy5YtNWXKFOXm5hZ5DlAGGcBFkpCQYEgyJBlr1qyxWZecnGyumz9/vtnet29fs/3C5cknnyx2f/Pnzzf71qhRo9D4zp07G3l5eYX6Xrh06NDB3OaaNWvs9vHw8DAyMjLs9pk5c6ZhGIZx8OBBw8/Pz2xPSEgwtztmzJgi9//YY4/ZPU/2jil/mxkZGYaXl5chyRgxYoQ5/s033zT7btq0ye55K7iPopY1a9YUeS4kGQ0aNDDOnDljbjO/vWbNmoX6BgYGGv/9738NwzCMPXv2FLnNrVu3GsePHzdatGhhd72fn5/xzTff2OyvqKVjx46GYRhGx44di+wzZ86cYn++AFQ85KnykacuPB9r1qwxzp49azRr1qzQ73nD+N/v+nr16hU5vrhzd+E2N2zYYLbNmjXL3ObIkSMNSYa3t7fx119/GfXq1St2e/nxFPy5u3B59tlni/0ZQtnCFSmUGWvXrtV7770nSYqNjdWff/6pXbt2qUmTJpKkSZMmaceOHQ5t67LLLtPOnTu1b98+xcbGSjo/bWPVqlWSpHbt2mnNmjXKzMzU2bNndfjwYQ0cOFCStG7dOvNqSIsWLfTrr7/q5MmTOnXqlF599VVJkmEYNu9KFTRz5kxJ0ltvvaXs7OxC6/fu3atx48ZJkgYMGKCDBw8qKytLgwYNkiS9/vrr+vnnnwuNa9q0qfbt26cdO3boiiuukCS9/fbb2rFjh+rWravbbrtNkvTOO++Y72gtXrxYktS8eXPFxMTYjXfMmDF2p7Ls2bPHbO/UqZMaNmyo5cuX66+//lJ2draOHj1q9v/999/1xRdfFNr233//rQ8++EBZWVkaP368JOnUqVN64YUXCvVNTk62mVbTqlUrTZs2TT/99JO8vLy0dOlSnTlzRjt37lSjRo2UnZ2tpKQkSbIZly8hIcFsy3/38amnntLPP/+so0ePKicnR9u3b1dkZKQk6bXXXrN7fgAgH3nKPXnKnjfeeEO//PKLw/1LMn/+fDNn1KtXz2bdtddeq2uuuUaSNG/ePLP9P//5jySpa9euioiI0N69ewtto2PHjmbb3r17JZ1/yMmWLVt05MgRnT17Vnv37jW3P3PmTJtchrKNQgplRsE/xJ999lnVqVNHjRo10vDhwyWdTworV650aFvDhw/XFVdcodq1a+vZZ58129esWSNJql27tpYuXar27durSpUqqlmzpt566y2zX34irFGjhg4dOqQqVaooMDBQgwcPVkBAgCZOnKhmzZrZ3fcvv/yilStXatasWXbXr1y50kwg8+fPV1hYmEJCQszEVjDOgpKTk1W7dm01btxYQ4YMMdvzi4T8BLd//3598cUXOnLkiLmdhISE4k+YA8LDw7VhwwbdcMMNCg0NVdWqVTV27Fhzvb0/Htq3b68+ffooODhYTz31lMLCwoo8Pns+//xzSVJubq7uuOMO+fv7q3Hjxvrtt98kSWlpaUX+oWBP9erV9dRTT5lTDZs2bWpOS3T0jx8AlRd5qmzkqSNHjmjMmDEl9vvjjz/MaXUDBgxwePv25Me+ceNG/fe//9WPP/6onTt3SlKhKZEliYiI0EsvvaRmzZrJ399fUVFR5nT1o0eP6uDBg6WKFZcOhRTKjMOHD5tf518luPDrQ4cOObStgmPq1q1baB/x8fGaMWOGdu/ebc7fLuj06dNFbvv06dNavny5Tp48WWhd586dJZ1/By89Pd18XZAjx3DkyJFCbSUd0w033KCmTZtKOv+O2bJly3Tu3Dl5eXmpb9++Je6zJE8++aReeOEFbd++XWfOnCm03t45Kxizp6enateubRNzSRw5VwXvFSjOH3/8oa5du+rzzz/X4cOHlZeXZ7Pe3jEBQEHkqf9xZ54aO3asjhw5ooiICF122WUOjyuNPn36qFq1apKkuXPnmlfSqlevrp49ezq8naysLHXp0kWLFy/WgQMH7N4TVdz3FmULhRTKjJo1a5pf//nnn+bXBR9kULBPcQqOKbitmjVrmglGOj+VID09XYZhmNMhLnTdddfJMAydPHlSK1askI+Pj9auXauFCxcW6tu7d2+FhYVp//79kmT3xtmCv/Q//PBDm2lphmEoLy/P7tOiijumfI899pgk6bPPPtObb74p6X9TDkrr448/lnT+nbTt27crLy9Pn376abFjCsacl5dnnhdHv4/55yooKEg5OTl2z9WFUzCK8uWXX5pXr0aMGKGTJ0/KMAy1bt3aofEAQJ4qG3nqxx9/lCSNHz9egYGB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pVjKG87dxVGurkg5Y+TIkTp27Ji5ZGRkuDskAEAlR24CgPKvXF2RioiIUGZmpk1bZmamQkJC7L7jJ0l+fn7y8/O7FOEBACohchMAVE7l6opUbGysUlJSbNpWrVql2NhYN0UEAKjsyE0AUDm5tZA6ceKEtm3bpm3btkk6/wjZbdu2KT09XdL5qQ/x8fFm/0ceeUS///67nnrqKf366696/fXX9dFHHykpKckd4QMAKiByEwDAEW4tpNLS0hQdHa3o6GhJ0rBhwxQdHa3Ro0dLkv766y8zcUlS/fr1tXz5cq1atUotW7bU5MmT9dZbb/F4WQCAy5CbAACOcOs9Up06dZJhGEWut/fJ8J06ddLWrVsvYlQAgMqM3AQAcES5ukcKAAAAAMoCCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACLKKQAAAAAwCIKKQAAAACwiEIKAAAAACyikAIAAAAAiyikAAAAAMAib3cHAAAAgIorasRym9d7J/RwUySAa3FFCgAAAAAsopACAAAAAIsopAAAAADAIgopAAAAALCIQgoAAAAALKKQAgAAAACL3F5IzZw5U1FRUfL391e7du20cePGYvtPmzZNV155pQICAhQZGamkpCSdOXPmEkULAKgMyE0AgJK4tZBatGiRhg0bpuTkZG3ZskUtW7ZU165ddfDgQbv9P/jgA40YMULJycnavn275s6dq0WLFumZZ565xJEDACoqchMAwBFuLaSmTJmihx56SAMGDFCzZs00a9YsBQYGat68eXb7r1+/Xh06dNB9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4cKF+vHHH3X33XerW7du2rVrl9nn1KlTevHFF/XOO+9o3bp1Onr0qO69994ij+Wvv/4yF0n6z3/+Y75u3769JCk4OFgLFizQL7/8ounTp2vOnDmaOnWqzXZ+++03ffTRR/r000+1YsUKbd26VY899phNn27dutns78svv5QkHT16VDfccIOio6OVlpamFStWKDMzU/fcc4+k88k6f8w999yje+65x3w9ffp0SdLJkyc1bNgwpaWlKSUlRZ6enrrjjjuUl5dX3LcSACqkipSnLvTnn39q2rRpCggIsHxeJOn555+3yUX5uUaS7r77buXm5ppFqCQdPHhQy5cv1wMPPKDevXub46ZNm6a6deuarzdt2iRJOnHihG6++WalpKRo69at6tatm3r27Kn09HSn4kUlYgAVTEJCgnHbbbcZhmEYeXl5xqpVqww/Pz/jiSeeKHJMjx49jOHDh9tdt3XrVsPf399YvXq1YRiGMX/+fCM0NNQYPHiw8fzzzxuGYRjffPON0aJFC+O2224zEhISDMMwjD/++MPw8vIy9u3bZ7O9G2+80Rg5cqS5LUnGd999Z67fvn27Icn4/vvvSzxWScaaNWtK7PfKK68YrVu3Nl8nJycbXl5exp9//mm2ffHFF4anp6fx119/GYZhex4vNG7cOKNLly42bRkZGYYkY8eOHTbtCQkJ5jkpzqFDhwxJxk8//VRiXwAozyp6ntqzZ48hydi6dathGIYRHx9vPPjgg0a9evWMqVOnmv0kGUuXLrUZ27FjR2PIkCHm6wvHGEbh/PToo48a3bt3N19PnjzZaNCggZGXl2czbv78+Ua9evXsxnyhq666ynj11Vcd6ovKiytSqJA+++wzBQUFyd/fX927d1fv3r01ZswYSVJubq7GjRunFi1aqHr16goKCtKXX35Z6J2nRx55RAEBAYqJidGoUaPUuXNnm/UPP/yw5s6dq7y8PM2ePVsPPfSQzfqffvpJubm5aty4sYKCgszl66+/tpnC5u3trTZt2pivmzRpoqpVq2r79u1OH/+iRYvUoUMHRUREKCgoSM8991yh47v88stt3pmMjY1VXl6eduzYUeL2f/jhB61Zs8bmuJo0aSJJDk/P27Vrl/r06aMGDRooJCTEnGLBO4AAKoPKkqe2bNmipUuXaty4cXbX9+nTx2bf33zzTYnbvNBDDz2klStXmlffFixYYE4xd8SJEyf0xBNPqGnTpqpataqCgoK0fft28hFKxMMmUCF17txZb7zxhnx9fVW7dm15e//vR/2VV17R9OnTNW3aNLVo0UJVqlTR0KFDzSkV+Z5//nk9/vjjWr16tcaMGaM77rhDTZs2Ndc3b95ctWvX1sKFC/XZZ59pxowZSklJMdefOHFCXl5e2rx5s7y8vGy2HRQUdJGOXNqwYYPuv/9+jR07Vl27dlVoaKgWLlyoyZMnu2wfJ06cUM+ePTVx4sRC62rVquXQNnr27Kl69eppzpw5ql27tvLy8tS8efNC3wcAqIgqS54aPny4nnjiiSJzw9SpUxUXF2e+vv/++y3vIzo6Wi1bttQ777yjLl266L///a+WL1/u8PgnnnhCq1at0qRJk9SoUSMFBATorrvuIh+hRBRSqJCqVKmiRo0a2V23bt063Xbbberbt68kKS8vTzt37lSzZs1s+oWFhSksLEzNmjXT3LlztXz5cpsEJUn//ve/9cgjj+j2229X1apVbdZFR0crNzdXBw8e1PXXX19krOfOnVNaWpratm0rSdqxY4eOHj1aaF+OWr9+verVq6dnn33WbPvjjz8K9UtPT9f+/ftVu3ZtSdJ3330nT09PXXnllSXu45prrtF//vMfRUVF2SR/R/3999/asWOH5syZY56bb7/91vJ2AKC8qgx56pNPPtHOnTuLLWoiIiJszoOz91ENHDhQ06ZN0759+xQXF6fIyEiHx65bt079+/fXHXfcIel8gbl3716n4kDlQiGFSueKK67Q4sWLtX79elWrVk1TpkxRZmammaCOHj2qZcuW6dprr5Wvr68+++wz/fTTT4qOji60rXvuuUcHDhwo9GQkSWrcuLHuv/9+xcfHa/LkyYqOjtahQ4eUkpKiq6++Wj169JAk+fj4aPDgwZoxY4a8vb2VmJioa6+91kxYzhxfenq6Fi5cqDZt2mj58uVaunRpoX7+/v5KSEjQpEmTlJWVpccff1z33HOPIiIiStzHoEGDNGfOHPXp00dPPfWUqlevrt9++00LFy7UW2+9VeidzQtVq1ZNNWrU0OzZs1WrVi2lp6drxIgRTh0vAFQ0FSVPvfzyy3r11VcVGBjogrNSvPvuu09PPPGE5syZo3feecfS2CuuuEJLlixRz5495eHhoVGjRvHgIziEe6RQ6Tz33HO65ppr1LVrV3Xq1EkRERHmY1QlyTAMLViwQLGxsWrevLlmz56tN954QzfeeGOhbQUEBOjpp58u8l25+fPnKz4+XsOHD9eVV16p22+/XZs2bdLll19u9gkMDNTTTz+t++67Tx06dFBQUJAWLVrk9PHdeuutSkpKUmJiolq1aqX169dr1KhRhfo1atRId955p26++WZ16dJFV199tV5//XWH9lG7dm2tW7dOubm56tKli1q0aKGhQ4eqatWq8vQs+deKp6enFi5cqM2bN6t58+ZKSkrSK6+8YvlYAaAiqih5qlGjRkpISLB+ApwQGhqqXr16KSgoyOZcOWLKlCmqVq2a2rdvr549e6pr16665pprLk6gqFA8DMMw3B0EUFktWLBAQ4cO1dGjRy/pfseMGaNly5bZfMYHAAAXcleecsaNN96oq666SjNmzHB3KKgkmNoHAACAcuuff/5RamqqUlNTHZ5ZAbgChRQAAADKrejoaP3zzz+aOHGiQw9MAlyFqX0AAAAAYBEPmwAAAAAAiyikAAAAAMAiCikAAAAAsIhCCgAAAAAsopACAAAAAIsopAAAAADAIgopXBL9+/eXh4dHicuCBQvcHSocNGbMGI0ZM0bLli2zPC7/+713796LElu+3377TQ899JCuuuoqeXp6mvs9c+bMRd0vgPKB3FTxlIfc9Omnn+r+++9X48aNFRISomrVqqlNmzaaP3++8vLyLuq+4Vp8IC8Ap4wdO1aSlJCQoNtvv929wRTh559/1ltvveXuMAAAl0h5yE0zZ87Ul19+adOWlpamBx54QGlpaZo5c6abIoNVXJHCJbdmzRoZhmEuycnJ7g4Jl0Bubq5ycnI0ZswY83sfFRV1UfdZp04dPfPMM/r000/Vtm3bi7ovAOUbualyckdu8vf3V1JSkn7++WedOnVKH3/8sby9z1/beOONN3Tw4MGLun+4DoUUyqy8vDy99tprio6OVmBgoKpUqaK2bds6NMViwYIF5iX6ZcuWacCAAapWrZpCQ0PVv39/HTt2zOy7adMm3XrrrYqKilKVKlXk6+urBg0aaPDgwfrnn3/Mfjt27FDHjh112WWXycfHR1WqVNE111yjRYsWmX1SU1PN/YaGhurEiRPmuvj4eHNd//79beL9+OOPdf311yskJET+/v5q2bKl3njjDRmGYfYpOO3g22+/1Z133qmgoCDVrFlTSUlJysnJkSSNGjXK7Ldr1y5z/KFDh+Tt7S0PDw8lJiYWee5Onjyp4cOHq2HDhgoICFDVqlXVokULDRw4UKdPnzbPbb6333670PSXgsc5Y8YMNWjQQL6+vvrll1/sTp8o+P1asmSJHnjgAVWrVk1hYWH697//rVOnTtnE+NFHH6lJkyYKCAhQbGysNm3apKioKHl4eKhTp05mvzZt2ujFF1/ULbfcooCAgCKPGQAcRW4iN5U2N7377ruaMmWKrrrqKgUEBOiuu+5St27dJEmGYWj37t1FngeUMQZwCSQkJBiSDEnGmjVrbNYlJyeb6+bPn2+29+3b12y/cHnyySeL3d/8+fPNvjVq1Cg0vnPnzkZeXl6hvhcuHTp0MLe5Zs0au308PDyMjIwMu31mzpxpGIZhHDx40PDz8zPbExISzO2OGTOmyP0/9thjds+TvWPK32ZGRobh5eVlSDJGjBhhjn/zzTfNvps2bSry3D3yyCNFxnPo0KFiz1f+9y//dfXq1W3Wb9261eY49uzZU+h7EBoaWmi7I0eONONbvXq14eHhYbM+ODjYCA4ONiQZHTt2tHtcHTt2NPufPn262J8fAJUDuYnc5O7clC8uLs4c98cffxTbF2UHV6RQJq1du1bvvfeeJCk2NlZ//vmndu3apSZNmkiSJk2apB07dji0rcsuu0w7d+7Uvn37FBsbK+n8FI5Vq1ZJktq1a6c1a9YoMzNTZ8+e1eHDhzVw4EBJ0rp167Rt2zZJUosWLfTrr7/q5MmTOnXqlF599VVJkmEYysrKsrvv/HnOb731lrKzswut37t3r8aNGydJGjBggA4ePKisrCwNGjRIkvT666/r559/LjSuadOm2rdvn3bs2KErrrhC0vl333bs2KG6devqtttukyS98847ys3NlSQtXrxYktS8eXPFxMQUeb6+/fZbSdLdd9+tEydO6J9//tHGjRs1evRo+fn5qX///jbvRiYkJJjTIS58N/PIkSMaPXq0/vnnH+3Zs0cNGjQocr/5qlevrp9//lm7d+9WRESEpPPviuYbPXq0uf/3339fR48e1b///W8dP368xG0DQGmQm8hNFyM3rV27VqtXr5YkxcXF6fLLLy9xDMoGCimUSV988YX59bPPPqs6deqoUaNGGj58uKTzCWLlypUObWv48OG64oorVLt2bT377LNm+5o1ayRJtWvX1tKlS9W+fXtVqVJFNWvWtHlAQX5SrFGjhg4dOqQqVaooMDBQgwcPVkBAgCZOnKhmzZrZ3fcvv/yilStXatasWXbXr1y50kwm8+fPV1hYmEJCQmxuNM2Ps6Dk5GTVrl1bjRs31pAhQ8z21NRUSTKT3f79+/XFF1/oyJEj5nYSEhKKPV/16tWTdD5Rv/DCC/rss89UpUoVjR07VsHBwcWOvVCTJk00duxYVa1aVVFRUQoJCSlxzBNPPKGrrrpKDRo00L/+9S9JUnp6uqTzc9m///57Seen7d13330KDQ3V888/Lx8fH0uxAYBV5CZyk6tz06ZNm3T77bcrLy9PderU0fz58y0dC9yLQgpl0uHDh82vIyMj7X596NAhh7ZVcEzdunUL7SM+Pl4zZszQ7t27zbncBZ0+fbrIbZ8+fVrLly/XyZMnC63r3LmzpPPv5qWnp5uvC3LkGI4cOVKoraRjuuGGG9S0aVNJ0rx587Rs2TKdO3dOXl5e6tu3b7H7mzRpklq0aKH9+/drwoQJ6tevn6666iq1bdtWR48eLTHegq6++mpL/SWZ72JK52/IlWR+Xw4fPqyzZ89Ksj3ugIAA1ahRw/K+AMAKctP/kJtKn5vWr1+vuLg4/fPPP6pdu7ZSUlJsxqPso5BCmVSzZk3z6z///NP8OiMjw26f4hQcU3BbNWvWNJONdH5aQXp6ugzDMKdGXOi6666TYRg6efKkVqxYIR8fH61du1YLFy4s1Ld3794KCwvT/v37JcnuTbSXXXaZ+fWHH35o88QowzCUl5dn98lRxR1Tvscee0yS9Nlnn+nNN9+UJHXt2tWcklCUJk2a6Mcff9Tu3bv16aefasyYMfLy8tKmTZssP5LVmQc8FHz3ruCNw5LMm6klmedVOv9Hw99//215XwBgBbmJ3CS5Jjd9/fXX6tq1q7KyshQVFaVvvvlGV155peW44F4UUiiT8p9eI0kvvfSS9u3bp99//11TpkyRdP6XWJcuXRza1pQpU/Tbb79p//79evHFF832zp0769y5c+b0hfynHe3YscPuL+V33nlHn3zyiQ4fPiwPDw/l5eWZv0ztvTPn5+enhx9+WNL5d+ny54YX1KVLF3l5eUk6PyUiLS1NOTk5+vPPPzVv3jxFR0fbPaZx48bpr7/+0q5duzR9+nSzveBTgeLj4xUUFKSzZ89q48aNklRonrg9L7/8spYuXSpvb2916dJFd999t/nuW8F3KatVqybp/IfeFvfOqCt5enqa9xJs3LhRS5YsUVZWlkaPHm2+G1hQ/n0FBd8tlKS///5bhw8fLvTEJQAoDrmJ3GSP1dy0atUqde/eXSdOnFDjxo31zTffOHSfFsoePpAXZVLHjh3Vp08fffjhh1q3bl2hS91JSUnmzb0lOXr0qM0leel8orrpppvk4eGhjh076uuvv9bWrVvNS/ANGzYstJ3Vq1fr7bffLtTu7e2tm2++2e6+hw8fro4dO6pWrVpmUiooKipKo0aN0pgxY7Rz5061adPGoWPatWuXateubdOWkJBg825WSEiI+vXrpzfeeEPS+eRy6623lrjtzz//XF9//bXddV27djW/btOmjVauXKl169YpMDDQjKtRo0YOHYOzxo4dqxtuuEGGYahXr16SpKCgIAUFBenEiRM27xSuW7fO7rSV/J+n5ORkjRkz5qLGC6DiIDcVj9zkWG568cUXzSJv586dNlMipfP3pTlSXML9uCKFMuu9997TtGnT1LJlS/n7+ysgIEAxMTGaO3euJk+e7PB23nzzTQ0cOFBVq1ZVcHCw4uPjtWTJEvOX2vvvv6/bbrtNwcHBCgsL04gRIzRy5MhC2+nYsaOio6MVGhoqLy8v1ahRQzfddJNWrlypq666yu6+q1atqri4uCLXS+f/mP/oo490/fXXKzg4WP7+/mrQoIF69eql999/3+6YJUuW6K677lJQUJCqV6+uIUOGaPbs2YX65U+hkKR7771Xfn5+xZ4r6fw7g126dFHt2rXl6+ur6tWrKzY2VgsXLlT37t3NfjNmzFCnTp0s3+RbWp06ddLChQt1xRVXyM/PT23bttXKlSvN72f16tUvaTwAKhdyE7nJHnJTJXWpnrMOXEoFP/vhws8GKa/sfcZFSVatWmWOSUtLu7gBXiInT540Vq9ebeTm5hqGYRjnzp0zJk6caB7nq6++6uYIAcA+ctN55CZUFEztAyqgpUuX6qmnnjI/nf2mm25S69at3RuUi2RlZemGG26Qn5+fLrvsMh05csS816l169Z68MEH3RwhAMAechMqGqb2ARXQsWPH9Ntvv8nPz089evTQu+++6+6QXCY4OFh9+vRRRESEDh06JMMw1KJFC40ZM0Zr16516mlMAICLj9yEisbDMAp8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+fv7q127dtq4cWOx/Y8ePapBgwapVq1a8vPzU+PGjfX55587cxgAANhFbgIAlMSpK1KGYbhk54sWLdKwYcM0a9YstWvXTtOmTVPXrl21Y8cOhYWFFeqfk5Ojm266SWFhYVq8eLHq1KmjP/74Q1WrVnVJPAAAkJsAAI5wqpBKTk52ybS+KVOm6KGHHtKAAQMkSbNmzdLy5cs1b948jRgxolD/efPm6ciRI1q/fr18fHwkSVFRUaWOAwCAfOQmAIAjnJraFx8fr3379hVq37Vrl/bu3evQNnJycrR582bFxcX9LxhPT8XFxWnDhg12x3zyySeKjY3VoEGDFB4erubNm+ull15Sbm6uM4cBAIANchMAwFFOFVL9+/fX+vXrC7V///336t+/v0PbOHz4sHJzcxUeHm7THh4ergMHDtgd8/vvv2vx4sXKzc3V559/rlGjRmny5Ml64YUXitxPdna2srKybBYAAOwhNwEAHOXU1L6tW7eqQ4cOhdqvvfZaJSYmljqoouTl5SksLEyzZ8+Wl5eXWrdurX379umVV14p8iEX48eP19ixYy9aTIAjokYst9u+d0KPSxwJAFcjNwFA5eT0U/uOHz9eqP3YsWMOT2WoWbOmvLy8lJmZadOemZmpiIgIu2Nq1aqlxo0by8vLy2xr2rSpDhw4oJycHLtjRo4cqWPHjplLRkaGQ/EBACofchMAwFFOFVL/+te/NH78eJuiKTc3V+PHj9d1113n0DZ8fX3VunVrpaSkmG15eXlKSUlRbGys3TEdOnTQb7/9pry8PLNt586dqlWrVpEfDuzn56eQkBCbBQAAe8hNAABHOVVITZw4UatXr9aVV16pAQMGaMCAAbryyiu1du1avfLKKw5vZ9iwYZozZ47efvttbd++XY8++qhOnjxpPikpPj5eI0eONPs/+uijOnLkiIYMGaKdO3dq+fLleumllzRo0CBnDgMAgELITQAARzh1j1SzZs30448/6rXXXtMPP/yggIAAxcfHKzExUdWrV3d4O71799ahQ4c0evRoHThwQK1atdKKFSvMm3zT09Pl6fm/Wi8yMlJffvmlkpKSdPXVV6tOnToaMmSInn76aWcOAwCAQshNAABHOFVISVLt2rX10ksvlTqAxMTEIh9QkZqaWqgtNjZW3333Xan3CwBAUchNAICSOF1IHT16VHPnztX27dsl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",
      "text/plain": [
       "<Figure size 1000x1000 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(10, 10))\n",
    "gs = grid(2, 2, figure=fig)\n",
    "\n",
    "ax = fig.add_subplot(gs[0, 0])\n",
    "ax.bar(sorted(df.target.unique()), l)\n",
    "ax.set_title('По размеру таргета', size=11, weight='heavy')\n",
    "ax.set_xlabel('Размер таргета', size=10)\n",
    "ax.set_ylabel('Вероятность', size=10)\n",
    "\n",
    "ax = fig.add_subplot(gs[0, 1])\n",
    "ax.bar(sorted(df['full_len'].unique()), l1)\n",
    "ax.set_title('По размеру инпута', size=11, weight='heavy')\n",
    "ax.set_xlabel('Размер инпута', size=10)\n",
    "\n",
    "ax = fig.add_subplot(gs[1, 0])\n",
    "ax.bar(sorted(df['s1_len'].unique()), l2)\n",
    "ax.set_title('По размеру string1', size=11, weight='heavy')\n",
    "ax.set_xlabel('Размер первой строки', size=10)\n",
    "ax.set_ylabel('Вероятность', size=10)\n",
    "\n",
    "ax = fig.add_subplot(gs[1, 1])\n",
    "ax.bar(sorted(df['s2_len'].unique()), l3)\n",
    "ax.set_title('По размеру string2', size=11, weight='heavy')\n",
    "ax.set_xlabel('Размер второй строки', size=10)\n",
    "\n",
    "plt.suptitle('Вероятности правильных ответов', size=15, weight='bold', x=0.5, y=.95)\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для размеров таргета тренд очевиден и уже указан, а вот для размера входных строк тренд хоть и прослеживается - меньше высоких столбцов, но все же содержит шумы, где даже для длинных строк ответ дается правильный, что также объяснимо хотя бы тем, что есть задания, где олна строка очень большая, а другая очень маленькая, а также задания, где две строки очень большие, но общих символов у них почти нет, что можно заметить глазами и дать правильный ответ"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
